package com.xunk.equipment.service.ruying;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Sets;
import com.xunk.common.core.constant.CacheConstants;
import com.xunk.common.core.constant.HttpStatus;
import com.xunk.common.core.constant.MessageNotifyConstants;
import com.xunk.common.core.constant.RuyingConstants;
import com.xunk.common.core.enums.DeviceEnum;
import com.xunk.common.core.enums.RuyingDeviceEnum;
import com.xunk.common.core.enums.SupplierEnum;
import com.xunk.common.core.utils.DataUtils;
import com.xunk.common.core.utils.HttpUtil;
import com.xunk.common.core.utils.StringUtils;
import com.xunk.common.core.utils.XunkAssert;
import com.xunk.common.redis.service.RedisService;
import com.xunk.equipment.api.domain.MessageNotificationDO;
import com.xunk.equipment.config.EquipmentConfig;
import com.xunk.equipment.domain.EquipmentBaseInfoDO;
import com.xunk.equipment.domain.EquipmentDO;
import com.xunk.equipment.domain.HomeDO;
import com.xunk.equipment.influxdb.InfluxCustomMapper;
import com.xunk.equipment.influxdb.entity.RuyingEquiqmentInfluxEntity;
import com.xunk.equipment.mapper.EquipmentMapper;
import com.xunk.equipment.mapper.HomeMapper;
import com.xunk.equipment.service.BaseService;
import com.xunk.equipment.service.IEquipmentNotifyService;
import com.xunk.equipment.vo.notify.NotifyAddedVO;
import com.xunk.equipment.vo.notify.NotifyRemovedVO;
import com.xunk.equipment.vo.notify.PropertyChangedVO;
import com.xunk.equipment.vo.notify.SummaryChangedVO;
import com.xunk.equipment.websocket.WebsocketManager;
import com.xunk.scene.api.RemoteSceneService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @ProjectName: xunk
 * @Package: com.xunk.equipment.service.ruying
 * @ClassName: EquipmentNotifyServiceImpl
 * @Author: yangwendong
 * @Description: 设备通知处理
 * @Date: 2021/3/29 15:20
 * @Version: 1.0
 */
@Slf4j
@Service(value = "ruyingEquipmentNotifyService")
public class EquipmentNotifyService implements IEquipmentNotifyService {

    @Autowired
    EquipmentMapper equipmentMapper;

    @Autowired
    HomeMapper homeMapper;

    @Autowired
    RedisService redisService;

    @Autowired
    BaseService baseService;

    @Autowired
    EquipmentConfig equipmentConfig;

    @Resource(name = "asyncServiceTaskExecutor")
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource(name = "asyncServiceTaskExecutor2")
    ThreadPoolTaskExecutor threadPoolTaskExecutor2;

    @Autowired
    InfluxCustomMapper influxCustomMapper;

    @Autowired
    RuYingControlService ruYingControlService;


    @Override
    public void equipmentNotify(String data) {
        if (DataUtils.isEmpty(data)) {
            return;
        }
        JSONObject obj = JSON.parseObject(data);
        String subType = obj.getString("subType");
        String payload = obj.getString("payload");
        if (DataUtils.isEmpty(subType) || DataUtils.isEmpty(payload)) {
            return;
        }
        log.info("============================");
        log.info("类型 【{}】, 回调信息 {}", subType, data);
        log.info("============================");
        CompletableFuture.runAsync(() -> {
            switch (subType) {
                case "device-added":
                    equipmentNotifyAdded(payload);
                    break;
                case "device-removed":
                    equipmentNotifyRemoved(payload);
                    break;
                case "device-summary-changed":
                    equipmentNotifySummaryChanged(payload);
                    break;
                case "device-properties-changed":
                    equipmentNotifyPropertyChanged(payload);
                    break;
                default:
            }
        }, threadPoolTaskExecutor).exceptionally(e -> {
            log.error("如影设备 通知异常 ", e);
            return null;
        });

    }

    /**
     * 新增设备
     *
     * @param payload
     */
    private void equipmentNotifyAdded(String payload) {
        // 有种情况：某个设备在网关下，操作人员直接将该设备添加到另一个网关下，如影的操作是先将该设备从原网关下删除
        // 在新添加到新网关下，为了防止请求顺序操作有误，添加处理接口延迟2s后再添加，让remove接口尽量处理完
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 解析data
        NotifyAddedVO ruyingEquipmentNotifyAddVO = JSON.parseObject(payload, new TypeReference<NotifyAddedVO>() {
        });
        String did = ruyingEquipmentNotifyAddVO.getDid();
        if (DataUtils.isEmpty(did)) {
            return;
        }
        String ownerId = ruyingEquipmentNotifyAddVO.getOwnerId();
        Optional<NotifyAddedVO.AddedSummary> summary = Optional.of(ruyingEquipmentNotifyAddVO).map(NotifyAddedVO::getSummary);
        // parentId为空的话，应该是网关，网关是前装人员手动录入的，这里不再操作
        String parentId = summary.map(NotifyAddedVO.AddedSummary::getParentId).orElse(null);
//        if (DataUtils.isEmpty(parentId)) {
//            return;
//        }


        String type = summary.map(NotifyAddedVO.AddedSummary::getType).orElse(null);
        RuyingDeviceEnum deviceEnum = RuyingDeviceEnum.parseDeviceEnumByThirdType(type);
        XunkAssert.isTrue(!Objects.equals(deviceEnum, RuyingDeviceEnum.UNKNOWN), "未知的设备被接入，请检查该设备是否确认被接入");
        log.info("设备--- {} 准备接入 did = {}", deviceEnum.getDeviceName(), did);
        Boolean online = summary.map(NotifyAddedVO.AddedSummary::getOnline).orElse(false);

        // 根据ownId 查询deptId
        Long deptId = baseService.findDeptIdByOwnId(ownerId);

        //  设备添加时增加设备影子（只要触发这个通知，就更新一下这个设备影子）
        Map<String, String> shadows = ruYingControlService.getShadows(ownerId, did);
        String ancestor = baseService.findAncestorByDeptId(deptId);
        redisService.setCacheMap(ancestor.concat(":").concat(did), shadows);

        // 根据did(deviceSn)查询是否已经存在该设备，如果存在，则不操作，如果不存在，则新增
        EquipmentDO equipment = equipmentMapper.getEquipment(did);
        if (ObjectUtil.isNotEmpty(equipment)) {
            if (Objects.equals(equipment.getParentSn(), parentId)) {
                log.info("设备已在库中，父网关对比一致。当前为添加设备操作，请检查该did对应的设备是否应该正确");
                return;
            } else {
                log.info("设备已在库中，父网关对比不一致，准备删除。");
                equipmentMapper.deleteEquipment(equipment.getDeviceSn(), equipment.getParentSn());
            }
        }

        // 根据type 查询设备定义
        EquipmentBaseInfoDO deviceBaseInfo = equipmentMapper.getDeviceBaseInfo(deviceEnum.getThirdType(), SupplierEnum.RUYING.getSupplierName());
        log.info("添加设备，产品定义 {}", JSON.toJSONString(deviceBaseInfo));
        String deviceName = "";
        Integer deviceSource = 0;
        Integer deviceCategory = 2;
        Long deviceBaseId = null;

        if (DataUtils.isEmpty(deviceBaseInfo)) {
            String defineDeviceUrl = equipmentConfig.getSpecUrl() + MessageFormat.format(RuyingConstants.RUYING_DEVICE_DEFINE, type);
            JSONObject defineDeviceResponse = HttpUtil.doGetstr(defineDeviceUrl);
            JSONObject defineDeviceData = Optional.ofNullable(defineDeviceResponse).map(s -> s.getJSONObject("data")).orElse(null);
            XunkAssert.notEmpty(defineDeviceData, "读取如影设备定义异常，type = " + type);
            // 将设备定义保存redis,暂时没用到这个缓存
            defineDeviceData.put("did", did);
            redisService.setCacheObject(MessageFormat.format(CacheConstants.RUYING_DEVICE_DEFINE_CACHE, ownerId, did), JSON.toJSONString(defineDeviceData));
            deviceName = Optional.of(defineDeviceData).map(s -> s.getJSONObject("description")).map(s -> s.getString("zh-CN")).orElse("未知");
        } else {
            deviceName = deviceBaseInfo.getBaseName();
            deviceSource = deviceBaseInfo.getBaseSource();
            deviceCategory = deviceBaseInfo.getBaseCategory();
            deviceBaseId = deviceBaseInfo.getId();
        }
        EquipmentDO equipmentDO = new EquipmentDO();
        equipmentDO.setDeviceSn(did);
        equipmentDO.setDeviceName(deviceName);
        equipmentDO.setDeviceNickname(equipmentDO.getDeviceName());
        equipmentDO.setDeptId(deptId);
        // 默认家庭逻辑
        equipmentDO.setDeviceSource(deviceSource);
        // 网关子设备
        equipmentDO.setDeviceCategory(deviceCategory);
        equipmentDO.setDeviceType(deviceEnum.getDeviceEnum().getDeviceType());
        equipmentDO.setBrand("如影");
        equipmentDO.setDeviceOnline(online ? 1L : 0L);
        equipmentDO.setSupplier(SupplierEnum.RUYING.getSupplierName());
        equipmentDO.setDeviceBaseId(deviceBaseId);
        equipmentDO.setParentSn(parentId);
        equipmentDO.setCreateBy("0");
        equipmentDO.setCreateTime(new Date());
        equipmentMapper.insertEquipment(equipmentDO);
        ruYingControlService.cacheRuyingEquipmentProperty(deptId, did, online ? "1" : "0");
    }


    private void equipmentNotifyRemoved(String payload) {
        // 解析data
        NotifyRemovedVO ruyingEquipmentNotifyRemovedVO = JSON.parseObject(payload, new TypeReference<NotifyRemovedVO>() {
        });
        String did = ruyingEquipmentNotifyRemovedVO.getDid();
        String ownerId = ruyingEquipmentNotifyRemovedVO.getOwnerId();
        if (DataUtils.isNotEmpty(did)) {

            NotifyRemovedVO.RemovedSummary summary = ruyingEquipmentNotifyRemovedVO.getSummary();
//            String parentId = summary.getParentId();
            // 根据ownId 查询deptId
            Long deptId = baseService.findDeptIdByOwnId(ownerId);

//            List<EquipmentDO> equipmentDOS = equipmentMapper.listEquipments(deptId, null);
//            List<String> parentSns = equipmentDOS.stream().filter(s -> DeviceEnum.GATEWAY.getDeviceType().equals(s.getDeviceType())).map(EquipmentDO::getDeviceSn).collect(Collectors.toList());
            // 设备移除时删除设备影子缓存
            String ancestor = baseService.findAncestorByDeptId(deptId);
            redisService.deleteObject(ancestor.concat(":").concat(did));
            // 删除设备基本信息缓存  还有一个地方需要删除设备基本信息，就是家庭修改设备信息，比如修改房间号之类的，直接删除(查询比修改更频繁，缓存的意义还在)
            redisService.deleteObject(CacheConstants.EQUIPMENT_INFO_KEY + ":" + did);
//            for (String parentSn : parentSns) {
            equipmentMapper.deleteEquipment(did, null);
//            }
        }
    }

    private void equipmentNotifySummaryChanged(String payload) {
        // 解析data
        SummaryChangedVO ruyingEquipmentNotifySummaryChangedVO = JSON.parseObject(payload, new TypeReference<SummaryChangedVO>() {
        });
        String did = ruyingEquipmentNotifySummaryChangedVO.getDid();
        Boolean online = Optional.ofNullable(ruyingEquipmentNotifySummaryChangedVO.getSummary()).map(SummaryChangedVO.ChangedSummary::getOnline).orElse(null);
        log.info("设备上下线 {}  {}", did, online);
        if (DataUtils.isNotEmpty(online)) {
            String ownerId = ruyingEquipmentNotifySummaryChangedVO.getOwnerId();
            Long deptId = baseService.findDeptIdByOwnId(ownerId);
            // 暂时也更新缓存
            ruYingControlService.cacheRuyingEquipmentProperty(deptId, did, "online", online ? "1" : "0");
            equipmentMapper.updateEquipmentOnline(did, online ? 1L : 0L);

            EquipmentDO equipmentInfo = baseService.getEquipmentInfo(did);
            List<Long> userIds = homeMapper.listUserIdsByDeptId(deptId);

            Map<String, Object> propertyMap = new HashMap<>();
            propertyMap.put("online", online ? "1" : "0");
            Map<String, Object> data = new HashMap<>();
            data.put("deviceSn", did);
            data.put("properties", propertyMap);
            data.put("deviceType", equipmentInfo.getDeviceType());

            Map<String, Object> pr = new HashMap<>();
            pr.put("code", HttpStatus.SUCCESS);
            pr.put("msg", "设备消息");
            pr.put("type", 3);
            pr.put("data", data);
            // 发送属性变化的推送
            userIds.forEach(userId -> sendSocketMessage(userId, JSON.toJSONString(pr)));
        }

    }

    private Set<DeviceEnum> alarmDeviceSets = Sets.newHashSet(DeviceEnum.GAS_SENSOR, DeviceEnum.SMOKE_SENSOR, DeviceEnum.WATER_SENSOR, DeviceEnum.DOOR_WINDOW_SENSOR);

    private Set<String> alarmDevicePropertySets = Sets.newHashSet(".10.11", ".10.14");

    private void equipmentNotifyPropertyChanged(String payload) {
        // 解析data
        PropertyChangedVO propertyChangedVO = JSON.parseObject(payload, new TypeReference<PropertyChangedVO>() {
        });
        String ownerId = propertyChangedVO.getOwnerId();

        List<PropertyChangedVO.ChangedProperty> properties = propertyChangedVO.getProperties();
        if (DataUtils.isNotEmpty(properties)) {
            List<MessageNotificationDO> message = new ArrayList<>();
            Map<String, Object> propertyMap = new HashMap<>();
            Long deptId = null;
            String did = null;
            DeviceEnum deviceEnum = DeviceEnum.UNKNOWN;
            for (PropertyChangedVO.ChangedProperty property : properties) {
                String pid = property.getPid();
                String value = property.getValue();
                if (DataUtils.isEmpty(value)) {
                    continue;
                }
                String[] ids = getIds(pid);
                if (DataUtils.isEmpty(deptId)) {
                    deptId = baseService.findDeptIdByOwnId(ownerId);
                }

                ruYingControlService.cacheRuyingEquipmentProperty(deptId, pid, value);
                // 并且记录到inluxdb中
                RuyingEquiqmentInfluxEntity entity = new RuyingEquiqmentInfluxEntity();
                did = ids[0];
                entity.setDeptId(deptId);
                entity.setDeviceId(did);
                entity.setServiceId(Integer.valueOf(ids[1]));
                entity.setPropertyId(Integer.valueOf(ids[2]));
                entity.setPropertyValue(value);
                influxCustomMapper.save(entity);

                // 4种传感器需要告警
                EquipmentDO equipmentInfo = baseService.getEquipmentInfo(did);
                deviceEnum = DeviceEnum.parseDeviceEnum(equipmentInfo.getDeviceType());
                String replace = pid.replaceAll(did, "");
                if (alarmDeviceSets.contains(deviceEnum) && alarmDevicePropertySets.contains(replace) && Objects.equals(value, "1")) {
                    HomeDO home = homeMapper.findMasterHomeByDeptId(equipmentInfo.getDeptId());
                    MessageNotificationDO messageN = new MessageNotificationDO();
                    messageN.setDeptId(deptId);
                    messageN.setType(MessageNotifyConstants.DEVICE_TYPE);
                    messageN.setCreateTime(new Date());
                    messageN.setTriggerId(did);
                    if (pid.contains(".10.14")) {
                        // 设备故障
                        messageN.setTitle("设备故障通知");
                        messageN.setContent(StringUtils.format(faultMessage, home.getHomeName(), deviceEnum.getDeviceName()));
                    } else if (pid.contains(".10.11")) {
                        // 设备检测报警
                        messageN.setTitle("设备告警通知");
                        messageN.setContent(StringUtils.format(alarmMessage, home.getHomeName(), deviceEnum.getDeviceName()));
                    }
                    message.add(messageN);
                }
                RuyingDeviceEnum ruyingDeviceEnum = RuyingDeviceEnum.parseDeviceEnumByDeviceType(deviceEnum);
                String p = ruyingDeviceEnum.getProperties().get(replace);
                Optional.ofNullable(p).ifPresent(s -> {
                    // 发送场景
                    notifyScene(ids[0], p, value);
                    String[] split = s.split("\\.");
                    if (split.length == 1) {
                        propertyMap.put(s, value);
                    } else {
                        Map<String, Object> second = new HashMap<>();
                        second.put(split[1], value);
                        propertyMap.put(split[0], second);
                    }
                });
            }
            // 发送
            if (MapUtils.isNotEmpty(propertyMap)) {
                // 查找当前home对应的user
                List<Long> userIds = homeMapper.listUserIdsByDeptId(deptId);
                Map<String, Object> data = new HashMap<>();
                data.put("deviceSn", did);
                data.put("properties", propertyMap);
                data.put("deviceType", deviceEnum.getDeviceType());

                Map<String, Object> pr = new HashMap<>();
                pr.put("code", HttpStatus.SUCCESS);
                pr.put("msg", "设备消息");
                pr.put("type", 3);
                pr.put("data", data);
                // 发送属性变化的推送
                userIds.forEach(userId -> sendSocketMessage(userId, JSON.toJSONString(pr)));
                // 发送告警或者故障消息
                message.forEach(item -> {
                    List<MessageNotificationDO> list = userIds.stream().map(userId -> {
                        MessageNotificationDO m = new MessageNotificationDO();
                        m.setTitle(item.getTitle());
                        m.setContent(item.getContent());
                        m.setDeptId(item.getDeptId());
                        m.setUserId(userId);
                        m.setTriggerId(item.getTriggerId());
                        m.setCreateTime(item.getCreateTime());
                        m.setType(item.getType());
                        return m;
                    }).collect(Collectors.toList());
                    baseService.insertMessageNotifications(list);
                });
            }
        }

    }

    private static final String faultMessage = "尊敬的用户，您的家庭\"{}\"中{}设备出现故障，请及时检查设备状态~";
    private static final String alarmMessage = "尊敬的用户，您的家庭\"{}\"中{}设备发出告警，请及时检查家庭安全情况~";

    private void sendSocketMessage(Long userId, String message) {
        WebsocketManager.appointSending(String.valueOf(userId), message);
    }

    @Autowired
    RemoteSceneService remoteSceneService;

    private void notifyScene(String deviceSn, String type, String value) {
        log.info("属性变化，发送场景。deviceSn = {}, type = {}, value = {}", deviceSn, type, value);
        CompletableFuture.runAsync(() -> {
            String o = type.replace(".o", "O");
            try {
                remoteSceneService.sceneNotify(deviceSn, o, value);
            } catch (Exception e) {
                log.error("属性变化，发送场景异常，deviceSn = {}, type = {}, value = {}", deviceSn, o, value);
            }
        }, threadPoolTaskExecutor2);
    }

    private String[] getIds(String pid) {

        //  pid 统一格式： 1@11.11.11
        String[] ids = new String[3];
        String[] iids = pid.split("\\.");
        ids[0] = iids[0];
        ids[1] = iids[1];
        ids[2] = iids[2];
        return ids;
    }

}
